/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License Version 2.0 with LLVM Exceptions
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *   https://llvm.org/LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once

// Get the library feature test macros:
#if __has_include(<version>)
#  include <version>
#elif __has_include(<ciso646>)
#  include <ciso646>
#endif

#include <cassert>

#if __has_include(<memory_resource>)
#  define UNIFEX_NO_MEMORY_RESOURCE 0
#  define UNIFEX_MEMORY_RESOURCE_HEADER <memory_resource>
#  define UNIFEX_MEMORY_RESOURCE_NAMESPACE std::pmr
#else
#  define UNIFEX_NO_MEMORY_RESOURCE 1
#endif

#if !defined(__has_cpp_attribute)
#define UNIFEX_NO_UNIQUE_ADDRESS
#elif !__has_cpp_attribute(no_unique_address)
#define UNIFEX_NO_UNIQUE_ADDRESS
// prior to clang-10, [[no_unique_address]] leads to bad codegen
#elif defined(__clang__) && __clang_major__ < 10
#define UNIFEX_NO_UNIQUE_ADDRESS
// GCC 10.3/11 introduced bug 98995, which fails to compile with no_uniq_addr.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98995
// For simplicity, assume anything above 9 is broken.
#elif !defined(__clang__) && defined(__GNUC__) && (__GNUC__ > 9)
#define UNIFEX_NO_UNIQUE_ADDRESS
#else
#define UNIFEX_NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif

#if !defined(UNIFEX_NO_COROUTINES)
#  if defined(__cpp_impl_coroutine) || defined(__cpp_coroutines)
#    define UNIFEX_NO_COROUTINES 0
#  else
#    define UNIFEX_NO_COROUTINES 1
#  endif
#endif

#if !UNIFEX_NO_COROUTINES
#  if __has_include(<coroutine>) && defined(__cpp_lib_coroutine)
#    define UNIFEX_COROUTINES_HEADER <coroutine>
#    define UNIFEX_COROUTINES_NAMESPACE std
#  elif __has_include(<experimental/coroutine>)
#    define UNIFEX_COROUTINES_HEADER <experimental/coroutine>
#    define UNIFEX_COROUTINES_NAMESPACE std::experimental
#  else
#    undef UNIFEX_NO_COROUTINES
#    define UNIFEX_NO_COROUTINES 1
#  endif
#endif

#if !defined(UNIFEX_NO_EPOLL)
#  if defined(__ANDROID_API__) && __ANDROID_API__ < 19
// Android makes timerfd_create and friend available as of API version 19;
// before that, the epoll API exists but it's insufficient for our purposes.
// https://android.googlesource.com/platform/bionic/+/master/libc/include/sys/timerfd.h#56
#    define UNIFEX_NO_EPOLL 1
#  endif
#endif

#if !defined(UNIFEX_NO_EPOLL)
#  if __has_include(<sys/epoll.h>)
#    define UNIFEX_NO_EPOLL 0
#  else
#    define UNIFEX_NO_EPOLL 1
#  endif
#endif

#if !defined(UNIFEX_NO_LIBURING)
#  if __has_include(<liburing/io_uring.h>)
#    define UNIFEX_LIBURING_HEADER <liburing/io_uring.h>
#  elif __has_include(<linux/io_uring.h>)
// some versions of gcc and clang: #define linux 1
#    if defined(linux)
#      undef linux
#    endif
#    define UNIFEX_LIBURING_HEADER <linux/io_uring.h>
#  else
#    undef UNIFEX_NO_LIBURING
#    define UNIFEX_NO_LIBURING 1
#  endif
#  if __has_include(<linux/time_types.h>)
#    define HAVE_LINUX_TIME_TYPES_H 1
#  else
#    define HAVE_LINUX_TIME_TYPES_H 0
#  endif
#endif

#if !defined(UNIFEX_ENABLE_CONTINUATION_VISITATIONS)
#define UNIFEX_ENABLE_CONTINUATION_VISITATIONS 0
#endif //UNIFEX_ENABLE_CONTINUATION_VISITATIONS

// UNIFEX_DECLARE_NON_DEDUCED_TYPE(type)
// UNIFEX_USE_NON_DEDUCED_TYPE(type)
//
// These macros work around a bug in MSVC that causes it to try to specialize
// all found function templates for which it can successfully deduce template
// arguments even if there are parameters that do not participate in template
// parameter deduction for which there is no conversion possible from the argument.
//
// This is most likely related to the core issue CWG1391 [*] which MSVC has not
// yet implemented as of VS 2019.4.
//
// These macros are intended to be used in template functions, typically tag_invoke()
// overloads, as follows.
//
// Where you would normally write:
//
// class foo_sender {
//    template<
//      typename Receiver,
//      std::enable_if_t<std::is_invocable_v<tag_t<set_value>, Receiver, foo>, int> = 0>
//    friend auto tag_invoke(tag_t<connect>, foo_sender&& s, Receiver&& r) -> foo_operation<Receiver> {
//      return ...;
//    }
// };
//
// You would instead write
//
// class foo_sender {
//   template<
//     typename Receiver,
//     UNIFEX_DECLARE_NON_DEDUCED_TYPE(CPO, tag_t<connect>),
//     UNIFEX_DECLARE_NON_DEDUCED_TYPE(S, foo_sender),
//     std::enable_if_t<std::is_invocable_v<tag_t<set_value>, Receiver, foo>, int> = 0?
//   friend auto tag_invoke(
//        UNIFEX_USE_NON_DEDUCED_TYPE(CPO, tag_t<connect>),
//        UNIFEX_USE_NON_DEDUCED_TYPE(S, foo_sender)&& s,
//        Receiver&& r) -> foo_operation<Receiver> {
//      return ...;
//    }
// };

#if defined(_MSC_VER)
# define UNIFEX_DECLARE_NON_DEDUCED_TYPE(NAME, ...) \
  typename NAME = void, \
  std::enable_if_t<std::is_same_v<NAME, __VA_ARGS__>, int> = 0
 # define UNIFEX_USE_NON_DEDUCED_TYPE(NAME, ...) NAME
#else
# define UNIFEX_DECLARE_NON_DEDUCED_TYPE(NAME, ...) typename NAME = __VA_ARGS__
# define UNIFEX_USE_NON_DEDUCED_TYPE(NAME, ...) __VA_ARGS__
#endif

#if !defined(UNIFEX_CXX_CONCEPTS)
#ifdef UNIFEX_DOXYGEN_INVOKED
#define UNIFEX_CXX_CONCEPTS 201800L
#elif defined(__cpp_concepts) && __cpp_concepts > 0
#define UNIFEX_CXX_CONCEPTS __cpp_concepts
#else
#define UNIFEX_CXX_CONCEPTS 0L
#endif
#endif

#if __cpp_rtti >= 199711
#  define UNIFEX_NO_RTTI 0
#else
#  define UNIFEX_NO_RTTI 1
#endif

#if __cpp_exceptions >= 199711
#  define UNIFEX_NO_EXCEPTIONS 0
#  define UNIFEX_TRY try
#  define UNIFEX_CATCH(...) catch (__VA_ARGS__)
#  define UNIFEX_RETHROW() throw
#else
#  define UNIFEX_NO_EXCEPTIONS 1
#  define UNIFEX_TRY
#  define UNIFEX_CATCH(...) if constexpr (true) {} else
#  define UNIFEX_RETHROW() ((void)0)
#endif

#if defined(_MSC_VER) && !defined(__clang__)
  #define UNIFEX_DIAGNOSTIC_PUSH __pragma(warning(push))
  #define UNIFEX_DIAGNOSTIC_POP __pragma(warning(pop))
  #define UNIFEX_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME
  #define UNIFEX_DIAGNOSTIC_IGNORE_FLOAT_EQUAL
  #define UNIFEX_DIAGNOSTIC_IGNORE_CPP2A_COMPAT
  #define UNIFEX_DIAGNOSTIC_IGNORE_UNUSED_RESULT
#else // ^^^ defined(_MSC_VER) ^^^ / vvv !defined(_MSC_VER) vvv
  #if defined(__GNUC__) || defined(__clang__)
    #define UNIFEX_PRAGMA(X) _Pragma(#X)
    #define UNIFEX_DIAGNOSTIC_PUSH UNIFEX_PRAGMA(GCC diagnostic push)
    #define UNIFEX_DIAGNOSTIC_POP UNIFEX_PRAGMA(GCC diagnostic pop)
    #define UNIFEX_DIAGNOSTIC_IGNORE_PRAGMAS \
      UNIFEX_PRAGMA(GCC diagnostic ignored "-Wpragmas")
    #define UNIFEX_DIAGNOSTIC_IGNORE(X) \
      UNIFEX_DIAGNOSTIC_IGNORE_PRAGMAS \
      UNIFEX_PRAGMA(GCC diagnostic ignored "-Wunknown-pragmas") \
      UNIFEX_PRAGMA(GCC diagnostic ignored X)
    #define UNIFEX_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME \
      UNIFEX_DIAGNOSTIC_IGNORE("-Wunknown-warning-option") \
      UNIFEX_DIAGNOSTIC_IGNORE("-Winit-list-lifetime")
    #define UNIFEX_DIAGNOSTIC_IGNORE_FLOAT_EQUAL \
      UNIFEX_DIAGNOSTIC_IGNORE("-Wfloat-equal")
    #define UNIFEX_DIAGNOSTIC_IGNORE_CPP2A_COMPAT \
      UNIFEX_DIAGNOSTIC_IGNORE("-Wc++2a-compat")
    #define UNIFEX_DIAGNOSTIC_IGNORE_UNUSED_RESULT \
      UNIFEX_DIAGNOSTIC_IGNORE("-Wunused-result")
  #else
    #define UNIFEX_DIAGNOSTIC_PUSH
    #define UNIFEX_DIAGNOSTIC_POP
    #define UNIFEX_DIAGNOSTIC_IGNORE_INIT_LIST_LIFETIME
    #define UNIFEX_DIAGNOSTIC_IGNORE_FLOAT_EQUAL
    #define UNIFEX_DIAGNOSTIC_IGNORE_CPP2A_COMPAT
    #define UNIFEX_DIAGNOSTIC_IGNORE_UNUSED_RESULT
  #endif
#endif // MSVC/Generic configuration switch

#if defined(__GNUC__) || defined(__clang__)
  #define UNIFEX_ALWAYS_INLINE \
    __attribute__((__always_inline__)) __attribute__((__visibility__("hidden"))) inline
#elif defined(_MSC_VER)
  #define UNIFEX_ALWAYS_INLINE __forceinline
#else
  #define UNIFEX_ALWAYS_INLINE inline
#endif

#if defined(__GNUC__) || defined(__clang__)
  #define UNIFEX_NO_INLINE __attribute__((__noinline__))
#elif defined(_MSC_VER)
  #define UNIFEX_NO_INLINE __declspec(noinline)
#else
  #define UNIFEX_NO_INLINE
#endif

#if defined(__GNUC__) || defined(__clang__)
  #define UNIFEX_ASSUME_UNREACHABLE __builtin_unreachable()
#elif defined(_MSC_VER)
  #define UNIFEX_ASSUME_UNREACHABLE __assume(0)
#else
  #define UNIFEX_ASSUME_UNREACHABLE std::terminate()
#endif

#ifndef UNIFEX_ASSERT
# define UNIFEX_ASSERT assert
#endif

#if defined(__clang__)
  // Clang's optimizer interacts badly with its address sanitizer. Certain
  // functions must have optimizations disabled in order to avoid triggering
  // stack-use-after-scope-exit errors.
  #if __has_feature(address_sanitizer)
    #define UNIFEX_CLANG_DISABLE_OPTIMIZATION __attribute__((__optnone__))
  #else
    #define UNIFEX_CLANG_DISABLE_OPTIMIZATION
  #endif
#else
  #define UNIFEX_CLANG_DISABLE_OPTIMIZATION
#endif

#if !defined(UNIFEX_DEPRECATED_HEADER)
  #ifdef __GNUC__
    #define UNIFEX_PRAGMA(X) _Pragma(#X)
    #define UNIFEX_DEPRECATED_HEADER(MSG) UNIFEX_PRAGMA(GCC warning MSG)
  #elif defined(_MSC_VER)
    #define UNIFEX_STRINGIZE_(MSG) #MSG
    #define UNIFEX_STRINGIZE(MSG) UNIFEX_STRINGIZE_(MSG)
    #define UNIFEX_DEPRECATED_HEADER(MSG) \
        __pragma(message(__FILE__ "(" UNIFEX_STRINGIZE(__LINE__) ") : Warning: " MSG))
  #endif
#else
  #define UNIFEX_DEPRECATED_HEADER(MSG) /**/
#endif

#if !defined(UNIFEX_LOG_DANGLING_STOP_CALLBACKS)
#  if !defined(NDEBUG)
// logging not already specified and this is a debug build so default on
#    define UNIFEX_LOG_DANGLING_STOP_CALLBACKS 1
#  else
// logging not already specified and this is a release build so default off
#    define UNIFEX_LOG_DANGLING_STOP_CALLBACKS 0
#  endif
#endif

#if defined(__has_builtin)
#  define UNIFEX_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__)
#else
#  define UNIFEX_HAS_BUILTIN(...) 0
#endif

#if !defined(UNIFEX_NO_ASYNC_STACKS)
// default:
//  - release builds do not have async stacks
//  - Windows builds do not have async stacks
//
// adding async stacks adds non-trivial binary size at the moment, and I can't
// figure out how to make all the relevant Windows builds succeed
#  if defined(NDEBUG) || defined(_MSC_VER)
#    define UNIFEX_NO_ASYNC_STACKS 1
#  else
#    define UNIFEX_NO_ASYNC_STACKS 0
#  endif
#endif  // !defined(UNIFEX_NO_ASYNC_STACKS)
